組込み現場の「C++」プログラミング 明日から使える徹底入門

高木 信尚(株式会社クローバーフィールド

1.2 組込み開発でC++を使う

PC向けのソフトウェア開発では1990年代にC++が急速に普及し,環境によっては,従来のCはほぼ完全にC++に置き換わってしまった感があります.一方,組込み向けの場合には,1990年代の終わり頃,すなわちC++の国際規格であるISO/IEC 14882:1998が制定されたあたりでは,ごく一部の分野を除いて,C++が使われることはまれでした.

ところが,ソフトウェアが大規模化したこともあってか,近年では組込み開発にC++が使われる機会もかなり増えてきたようです.経済産業省による調査報告でも,行数比率では,C++は他の言語を圧倒しています(図1.1参照).

●図1.1 組込み開発でのプログラミング言語使用比率と行数比率(開発行数×使用比率)

図1.1 組込み開発でのプログラミング言語使用比率と行数比率(開発行数×使用比率)

図1.1 組込み開発でのプログラミング言語使用比率と行数比率(開発行数×使用比率)

*「経済産業省 2008年版 組込みソフトウェア産業実態調査報告書 プロジェクト責任者向け調査」より

この調査結果からもわかるように,開発行数の多い,すなわち大規模の開発ではC++がかなり使われていることがわかります.一方,Cの比率は依然として高く,C++を使う場合でも,C++単独で使うというよりはCと併用することが多いのではないでしょうか? もちろん,プロセッサの込み入った制御にはアセンブラが使われることもあるでしょうから,組込み開発では,複数言語が混在する機会がどうしても多くなります.

何年か前までは,組込み開発でC++を行おうとしても,使用するプロセッサ向けのC++コンパイラが入手できなかったり,コンパイラの信頼性に不安があったりと,なかなか手を出しにくい状況でした.また,すでに国際標準ができているにもかかわらず処理系の標準準拠度が低い状況もあり,処理系間の移植性の低さも大きな問題でした.

しかし,近年ではC++コンパイラの標準準拠度も向上し,少なくとも標準C++を目指している処理系間の移植性はずいぶん向上しました.その一方で,特定環境向けの方言も依然として存在しています.しかし,こうした方言については,多かれ少なかれCの場合にも存在します.その意味で,C++の処理系に関する問題は,(同程度とまではいきませんが)Cの事情にかなり近づいてきました.

1.2.1 C++を使う場合のメリットとデメリット

C++を使うメリットは少なくありません.もちろん,それらのメリットは組込み開発においても有効です.C++を使う主なメリットを以下に挙げてみます.

  • (A) オブジェクト指向プログラミングを支援するための言語機能が備わっている.
  • (B) ジェネリックプログラミングを支援するための言語機能が備わっている.
  • (C) 静的なエラー検出機能が優れている.
  • (D) 関数や演算子の多重定義ができる.
  • (E) 名前空間が使用できる.
  • (F) リソース管理が容易になり,バグの軽減に繋がる.

このうち,(A)のオブジェクト指向プログラミングとBのジェネリックプログラミングという手法を支援する機能は非常に強力です.しかも,C++は,ソフトウェア開発者に対して,特定の手法を強制するようなことはありません.必要に応じて,そして開発現場の実情に応じて,適切な手法をそのつど選択することができますし,部分部分で手法を切り替えることも,複数の手法を混在させることも,容易にできるのです.

次に,C++を使うことによるデメリットを挙げてみます.

  • (A) Cコンパイラしかない環境には移植できない.
  • (B) C++処理系間の方言や標準準拠度の差が存在する.
  • (C) コンパイラの最適化性能によっては,プログラムサイズが肥大化する.
  • (D) 言語仕様を十分理解せずに使用すると危険がある.

それではデメリットを1つずつ見ていきましょう.上記のうち,(A)と(B)は,主に移植に関する問題点です.そして,(C)と(D)は,移植だけでなく,新規開発を行う場合にも直面する問題点です.

まず,(A)ですが,Cコンパイラしかない環境に移植できないのは当然のことです.しかし,近年では,開発ツールのC++対応状況もかなりよくなってきました.また,C++コンパイラが存在しない環境というのは,比較的ローエンドのものが多く,C++で開発したソフトウェアを移植するときは,よりハイエンドな環境に移行することのほうが圧倒的に多いはずですので,実際に問題になることはそれほど多くないでしょう.

次に,(B)のC++処理系間の方言や標準準拠度の差が存在する問題です.前述のとおり,標準準拠度については,近年かなり状況が良くなってきました.

(C)のコンパイラの最適化性能によってはプログラムサイズが肥大化してしまう問題についてですが,これは主に例外処理に関係する現象です.例外処理については,本章の「1.3.7 例外処理」や「3.7 例外処理の裏側」で詳しく解説します.この問題は深刻ですが,回避する方法はありますので,それについても,第5章の「5.4.1 例外指定を活用する」,「5.4.2 例外を送出しない関数をインライン関数にする」,「5.4.3 必要のないデストラクタは定義しない」で詳しく解説します.

最後の(D)の言語仕様を十分理解せずに使用すると危険がある点についてですが,これはCも同じです.ただし,Cより言語機能が多い分,理解すべき内容も多いですし,それに伴って理解不足によって発生する危険も多いといえます.

1.2.2 C++の静的なエラー検出機能

C++といえば「オブジェクト指向」と考える方も多いかもしれませんが,たとえCとほとんど同じ使い方しかしない場合でも,C++の静的なエラー検出機能によって,潜在的なバグを発見することができます.Cでは,明らかに間違った使い方をしている場合でもエラーにならなかったコードが,C++ではことごとくコンパイルエラーになるのです.

たとえば,Cでは,ポインタ型と汎整数型は暗黙的に相互変換ができてしまいます.文字列(charへのポインタ)を渡さなければならない関数に整数値を渡したとしても,コンパイラはエラーにしません.処理系によっては警告ぐらいは出るかもしれませんが,規格で保証されているわけではありません.しかし,C++では,明示的にキャストしないかぎり,このような変換はコンパイルエラーになります.

別の例を挙げてみましょう.Cでは,関数を呼び出すときに関数原型*3(プロトタイプ)は必須ではありません.その結果,何ごともなかったかのようにコンパイル~リンクが成功し,実際に動かしてみて,初めて,不可解な動作をすることに気づくことになります.しかし,C++では関数原型のない関数呼び出しはコンパイルエラーになります.しかも,関数原型と実際の関数定義が矛盾していた場合,コンパイルは通ったとしてもリンク時にエラーになります.

このように,C++では,可能なかぎり,コンパイル時に,それが無理でもリンク時にエラーを検出することを試みます.この機能だけでも,C++を使うメリットは十分にあるのです.静的なエラー検出機能については,「1.3.1 Cとまったく同じコードをC++で書く」で詳しく解説します.

*3 仮引数の型を含めた関数の宣言のことです.「プロトタイプ宣言」のほうがなじみが深いかもしれませんが,本書では規格用語を用いて「関数原型」と表記します.

1.2.3 C++を使えない状況

これまでざっと見てきただけでもC++を使うメリットは十分にあるわけですが,それでもC++を使えない状況がいくつかあります.

  • (A) C++コンパイラが使えない場合
  • (B) Cだけで開発するアプリケーションで利用するライブラリを開発する場合
  • (C) スタートアップを記述する場合

このうち,(A)のC++コンパイラがない場合にC++が使えないのは当然です.(B)のCだけで開発するアプリケーションで利用するライブラリを開発する場合にC++を利用できないのは,C++を使う場合にはスタートアップにひと工夫を加えたり,リンカが行うべき処理が異なったりするためです.C++で書かれたコードが混在することを想定していない,あるいはそれを望まないアプリケーション開発でも利用する可能性があるライブラリやミドルウェアなどの開発には,C++は使用できません.

(C)のスタートアップが記述できないのは,アセンブリ言語でないと記述できない特殊なCPUの命令を使うからとか,そういうことではありません.C++では,main関数を呼び出すことも,main関数へのポインタを取得することもできないのです.そのため,main関数を呼び出す必要があるスタートアップは,C++で記述することができません.